/**
 * Generali Solutions d'assurances - Tous droits réservés &copy; 2007 - 2010
 */

package fr.generali.accueilclient.commun.rce;

import java.beans.PropertyDescriptor;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.datatype.XMLGregorianCalendar;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.collections.Transformer;
import org.junit.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;

/**
 * @author Holmes Kelly
 */

public abstract class AbstractBcRceJUnit4SpringContextTestsCase extends AbstractJUnit4SpringContextTests {

    /**
     * LOGGER
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractBcRceJUnit4SpringContextTestsCase.class);

    /**
     * @param objectExpect l'objet attendu
     * @param objectTest l'objet test
     * @throws Exception exception has occurred
     */
    protected void compareBeanFields(Object objectExpect, Object objectTest) throws Exception {

        BeanWrapper beanWrapperExpect = new BeanWrapperImpl(objectExpect);

        BeanWrapper beanWrapperTest = new BeanWrapperImpl(objectTest);

        PropertyDescriptor[] propertyDescriptors = beanWrapperExpect.getPropertyDescriptors();

        Map<String, Class< ? >> mapTypeSemiPrimitifs = getMapTypeSemiPrimitifs();

        Map<String, Class< ? >> mapTypeList = getMapTypeList();

        for (PropertyDescriptor propertyDescriptorLoc : propertyDescriptors) {

            Object propertyValueExpect = beanWrapperExpect.getPropertyValue(propertyDescriptorLoc.getName());
            Object propertyValueTest = beanWrapperTest.getPropertyValue(propertyDescriptorLoc.getName());

            if (mapTypeSemiPrimitifs.values().contains(propertyDescriptorLoc.getPropertyType())) {

                Assert.assertEquals(propertyValueExpect, propertyValueTest);

            }

            if (mapTypeList.values().contains(propertyDescriptorLoc.getPropertyType())) {

                if (propertyValueExpect != null && propertyValueTest != null) {
                    compareBeans(propertyValueExpect, propertyValueTest);
                }
            }
        }
    }

    protected void printBeanFields(Object objectExpect, Object objectTest) throws Exception {

        BeanWrapper beanWrapperExpect = new BeanWrapperImpl(objectExpect);

        BeanWrapper beanWrapperTest = new BeanWrapperImpl(objectTest);

        PropertyDescriptor[] propertyDescriptors = beanWrapperExpect.getPropertyDescriptors();

        Map<String, Class< ? >> mapTypeSemiPrimitifs = getMapTypeSemiPrimitifs();

        Map<String, Class< ? >> mapTypeList = getMapTypeList();

        for (PropertyDescriptor propertyDescriptorLoc : propertyDescriptors) {

            Object propertyValueExpect = beanWrapperExpect.getPropertyValue(propertyDescriptorLoc.getName());
            Object propertyValueTest = beanWrapperTest.getPropertyValue(propertyDescriptorLoc.getName());

            if (mapTypeSemiPrimitifs.values().contains(propertyDescriptorLoc.getPropertyType())) {

                LOGGER.info(String.format("value expect=%s", propertyValueExpect));
                LOGGER.info(String.format("value test=%s", propertyValueTest));
            }

            if (mapTypeList.values().contains(propertyDescriptorLoc.getPropertyType())) {

                if (propertyValueExpect != null && propertyValueTest != null) {
                    printBeans(propertyValueExpect, propertyValueTest);
                }
            }
        }
    }

    /**
     * @return mapType une map
     */
    protected Map<String, Class< ? >> getMapTypeSemiPrimitifs() {

        Map<String, Class< ? >> mapType = new HashMap<String, Class< ? >>();

        MapUtils.safeAddToMap(mapType, String.class.getName(), String.class);
        MapUtils.safeAddToMap(mapType, BigInteger.class.getName(), BigInteger.class);
        MapUtils.safeAddToMap(mapType, BigDecimal.class.getName(), BigDecimal.class);
        MapUtils.safeAddToMap(mapType, XMLGregorianCalendar.class.getName(), XMLGregorianCalendar.class);
        MapUtils.safeAddToMap(mapType, Date.class.getName(), Date.class);

        return mapType;

    }

    /**
     * @return mapType une map
     */
    protected Map<String, Class< ? >> getMapTypeList() {

        Map<String, Class< ? >> mapType = new HashMap<String, Class< ? >>();

        MapUtils.safeAddToMap(mapType, List.class.getName(), List.class);
        MapUtils.safeAddToMap(mapType, Map.class.getName(), Map.class);
        return mapType;

    }

    /**
     * @param objectExpect expect object
     * @param objectTest test object
     * @throws Exception exception
     */
    protected void compareBeans(Object objectExpect, Object objectTest) throws Exception {

        if (objectExpect instanceof Collection< ? > && objectTest instanceof Collection< ? >) {

            Iterator< ? > iteratorExpect = ((Collection< ? > ) objectExpect).iterator();

            Iterator< ? > iteratorTest = ((Collection< ? > ) objectTest).iterator();

            while (iteratorExpect.hasNext() && iteratorTest.hasNext()) {

                Object itemExpect = iteratorExpect.next();

                Object itemTest = iteratorTest.next();

                compareBeanFields(itemExpect, itemTest);

            }
        } else if (objectExpect instanceof Map< ? , ? > && objectTest instanceof Map< ? , ? >) {

            Map< ? , ? > mapExpect = (Map< ? , ? > ) objectExpect;
            Set< ? > keySetExpect = mapExpect.keySet();
            Map< ? , ? > mapTest = (Map< ? , ? > ) objectTest;
            Set< ? > keySetTest = mapTest.keySet();

            if (mapExpect.size() == mapTest.size() && keySetExpect.size() == keySetTest.size()) {

                for (Object keyMap : keySetExpect) {
                    Object itemExpect = mapExpect.get(keyMap);
                    Object itemTest = mapTest.get(keyMap);
                    compareBeanFields(itemExpect, itemTest);
                }

            }
        }

        else {

            compareBeanFields(objectExpect, objectTest);
        }

    }

    protected void printBeans(Object objectExpect, Object objectTest) throws Exception {

        if (objectExpect instanceof Collection< ? > && objectTest instanceof Collection< ? >) {

            Iterator< ? > iteratorExpect = ((Collection< ? > ) objectExpect).iterator();

            Iterator< ? > iteratorTest = ((Collection< ? > ) objectTest).iterator();

            while (iteratorExpect.hasNext() && iteratorTest.hasNext()) {

                Object itemExpect = iteratorExpect.next();

                Object itemTest = iteratorTest.next();

                printBeanFields(itemExpect, itemTest);

            }
        } else if (objectExpect instanceof Map< ? , ? > && objectTest instanceof Map< ? , ? >) {

            Map< ? , ? > mapExpect = (Map< ? , ? > ) objectExpect;
            Set< ? > keySetExpect = mapExpect.keySet();
            Map< ? , ? > mapTest = (Map< ? , ? > ) objectTest;
            Set< ? > keySetTest = mapTest.keySet();

            if (mapExpect.size() == mapTest.size() && keySetExpect.size() == keySetTest.size()) {

                for (Object keyMap : keySetExpect) {
                    Object itemExpect = mapExpect.get(keyMap);
                    Object itemTest = mapTest.get(keyMap);
                    printBeanFields(itemExpect, itemTest);
                }

            }
        }

        else {

            printBeanFields(objectExpect, objectTest);
        }

    }

    /**
     * @param classBean class
     * @return transformer
     */
    protected Transformer getTransformerBean(Class< ? > classBean) {

        final Class< ? > classBeanTrans = classBean;

        Transformer transformer = new Transformer() {

            public Object transform(Object input) {

                return classBeanTrans.cast(input);
            }
        };

        return transformer;
    }

    /**
     * @param <T> type parametre
     * @param beanId dans application context
     * @param classBean class du bean
     * @return list
     */
    protected <T> List<T> createInstanceListObject(String beanId, Class<T> classBean) {

        List<T> list = new ArrayList<T>();

        Object bean = applicationContext.getBean(beanId);

        if (bean != null && bean instanceof List< ? >) {

            List< ? > listBean = (List< ? > ) bean;

            CollectionUtils.collect(listBean, getTransformerBean(classBean), list);
        }

        return list;
    }

    /**
     * @param <T> type parametre
     * @param beanId dans application context
     * @param classBean class du bean
     * @return bean
     */
    protected <T> T createInstanceObject(String beanId, Class<T> classBean) {

        T target = null;

        Object bean = applicationContext.getBean(beanId);

        if (bean != null && bean.getClass().getName().equals(classBean.getName())) {

            target = classBean.cast(bean);
        }

        return target;

    }

}
